/**
 * DropKick
 *
 * Highly customizable <select> lists
 * https://github.com/JamieLottering/DropKick
 *
 * &copy; 2011 Jamie Lottering <http://github.com/JamieLottering>
 *                        <http://twitter.com/JamieLottering>
 * 
 */
(function ($, window, document) {

    document.documentElement.className = document.documentElement.className + ' dk_fouc';

    var
      // Public methods exposed to $.fn.dropkick()
      methods = {},

      // Cache every <select> element that gets dropkicked
      lists = [],

      // Convenience keys for keyboard navigation
      keyMap = {
          'left': 37,
          'up': 38,
          'right': 39,
          'down': 40,
          'enter': 13
      },

      // HTML template for the dropdowns
      dropdownTemplate = [
        '<div class="dk_container {{ classname }}" id="dk_container_{{ id }}" tabindex="{{ tabindex }}">',
          '<a class="dk_toggle">',
            '<span class="dk_label">{{ label }}</span>',
            '<span class="select-icon"></span>',
          '</a>',
          '<div class="dk_options">',
            '<ul class="dk_options_inner">',
            '</ul>',
          '</div>',
        '</div>'
      ].join(''),

      // HTML template for dropdown options
      optionTemplate = '<li class="{{ current }}"><a data-dk-dropdown-value="{{ value }}">{{ text }}</a></li>',

      // Some nice default values
      defaults = {
          startSpeed: 100,  // I recommend a high value here, I feel it makes the changes less noticeable to the user
          theme: false,
          change: false
      },

      // Make sure we only bind keydown on the document once
      keysBound = false
    ;

    // Called by using $('foo').dropkick();
    methods.init = function (settings) {
        settings = $.extend({}, defaults, settings);

        return this.each(function () {
            var
              // The current <select> element
              $select = $(this),

              // Store a reference to the originally selected <option> element
              $original = $select.find(':selected').first(),

              // Save all of the <option> elements
              $options = $select.find('option'),

              // We store lots of great stuff using jQuery data
              data = $select.data('dropkick') || {},

              // This gets applied to the 'dk_container' element
              id = $select.attr('id') || $select.attr('name'),

              // This gets updated to be equal to the longest <option> element
              width = settings.width || $select.outerWidth(),

              // Check if we have a tabindex set or not
              tabindex = $select.attr('tabindex') ? $select.attr('tabindex') : '',

              // Check if we have a class name set or not
              classname = $select.attr('class') ? $select.attr('class') : '',

              // The completed dk_container element
              $dk = false,

              theme
            ;

            // Dont do anything if we've already setup dropkick on this element
            if (data.id) {
                return $select;
            } else {
                data.settings = settings;
                data.tabindex = tabindex;
                data.classname = classname;
                data.id = id;
                data.$original = $original;
                data.$select = $select;
                data.value = _notBlank($select.val()) || _notBlank($original.attr('value'));
                data.label = $original.text();
                data.options = $options;
            }

            // Build the dropdown HTML
            $dk = _build(dropdownTemplate, data);

            // Make the dropdown fixed width if desired
            $dk.find('.dk_toggle').css({
                // Disable inline width since it should fill all available parrent space
                // 'width' : width + 'px'
            });

            // Hide the <select> list and place our new one in front of it
            $select.before($dk);

            // Update the reference to $dk
            $dk = $('#dk_container_' + id).addClass('dk_shown');

            // Save the current theme
            theme = settings.theme ? settings.theme : 'default';
            $dk.addClass('dk_theme_' + theme);
            data.theme = theme;

            if ($select.is(':disabled') || $select.is('[readonly]'))
                $dk.addClass('disabled');
            else
                $dk.removeClass('disabled');

            // Save the updated $dk reference into our data object
            data.$dk = $dk;

            // Save the dropkick data onto the <select> element
            $select.data('dropkick', data);

            // Do the same for the dropdown, but add a few helpers
            $dk.data('dropkick', data);

            lists[lists.length] = $select;

            // Focus events
            $dk.bind('focus.dropkick', function (e) {
                $dk.addClass('dk_focus');
            }).bind('blur.dropkick', function (e) {
                $dk.removeClass('dk_open dk_focus');
            });

            setTimeout(function () {
                $select.hide();
            }, 0);
        });
    };

    // Allows dynamic theme changes
    methods.theme = function (newTheme) {
        var
          $select = $(this),
          list = $select.data('dropkick'),
          $dk = list.$dk,
          oldtheme = 'dk_theme_' + list.theme
        ;

        $dk.removeClass(oldtheme).addClass('dk_theme_' + newTheme);

        list.theme = newTheme;
    };

    // Reset all <selects and dropdowns in our lists array
    methods.reset = function () {
        for (var i = 0, l = lists.length; i < l; i++) {
            var
              listData = lists[i].data('dropkick'),
              $dk = listData.$dk,
              $current = $dk.find('li').first()
            ;

            $dk.find('.dk_label').text(listData.label);
            $dk.find('.dk_options_inner').animate({ scrollTop: 0 }, 0);

            _setCurrent($current, $dk);
            _updateFields($current, $dk, true);
        }
    };

    // Expose the plugin
    $.fn.dropkick = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        }
    };

    // private
    function _handleKeyBoardNav(e, $dk) {
        var
          code = e.keyCode,
          data = $dk.data('dropkick'),
          options = $dk.find('.dk_options'),
          open = $dk.hasClass('dk_open'),
          current = $dk.find('.dk_option_current'),
          first = options.find('li').first(),
          last = options.find('li').last(),
          next,
          prev
        ;

        switch (code) {
            case keyMap.enter:
                if (open) {
                    _updateFields(current.find('a'), $dk);
                    _closeDropdown($dk);
                } else {
                    _openDropdown($dk);
                }
                e.preventDefault();
                break;

            case keyMap.up:
                prev = current.prev('li');
                if (open) {
                    if (prev.length) {
                        _setCurrent(prev, $dk);
                    } else {
                        _setCurrent(last, $dk);
                    }
                } else {
                    _openDropdown($dk);
                }
                e.preventDefault();
                break;

            case keyMap.down:
                if (open) {
                    next = current.next('li').first();
                    if (next.length) {
                        _setCurrent(next, $dk);
                    } else {
                        _setCurrent(first, $dk);
                    }
                } else {
                    _openDropdown($dk);
                }
                e.preventDefault();
                break;

            default:
                break;
        }
    }

    // Update the <select> value, and the dropdown label
    function _updateFields(option, $dk, reset) {
        var value, label, data;

        value = option.attr('data-dk-dropdown-value');
        label = option.text();
        data = $dk.data('dropkick');

        $select = data.$select;
        $select.val(value).trigger('change');

        $dk.find('.dk_label').text(label);

        reset = reset || false;

        if (data.settings.change && !reset) {
            data.settings.change.call($select, value, label);
        }
    }

    // Set the currently selected option
    function _setCurrent($current, $dk) {
        $dk.find('.dk_option_current').removeClass('dk_option_current');
        $current.addClass('dk_option_current');

        _setScrollPos($dk, $current);
    }

    function _setScrollPos($dk, anchor) {
        var height = anchor.prevAll('li').outerHeight() * anchor.prevAll('li').length;
        $dk.find('.dk_options_inner').animate({ scrollTop: height + 'px' }, 0);
    }

    // Close a dropdown
    function _closeDropdown($dk) {
        $dk.removeClass('dk_open');
    }

    // Open a dropdown
    function _openDropdown($dk) {
        var data = $dk.data('dropkick');
        $dk.find('.dk_options').css({ top: $dk.find('.dk_toggle').outerHeight() - 1 });
        $dk.toggleClass('dk_open');

    }

    /**
     * Turn the dropdownTemplate into a jQuery object and fill in the variables.
     */
    function _build(tpl, view) {
        var
          // Template for the dropdown
          template = tpl,
          // Holder of the dropdowns options
          options = [],
          $dk
        ;

        template = template.replace('{{ id }}', view.id);
        template = template.replace('{{ label }}', view.label);
        template = template.replace('{{ tabindex }}', view.tabindex);
        template = template.replace('{{ classname }}', view.classname);

        if (view.options && view.options.length) {
            for (var i = 0, l = view.options.length; i < l; i++) {
                var
                  $option = $(view.options[i]),
                  current = 'dk_option_current',
                  oTemplate = optionTemplate
                ;

                oTemplate = oTemplate.replace('{{ value }}', $option.val());
                oTemplate = oTemplate.replace('{{ current }}', (_notBlank($option.val()) === view.value) ? current : '');
                oTemplate = oTemplate.replace('{{ text }}', $option.text());

                options[options.length] = oTemplate;
            }
        }

        $dk = $(template);
        $dk.find('.dk_options_inner').html(options.join(''));

        return $dk;
    }

    function _notBlank(text) {
        return ($.trim(text).length > 0) ? text : false;
    }

    $(function () {

        // Handle click events on the dropdown toggler
        $(document).on('click', '.dk_toggle', function (e) {
            if ($(this).closest('.dk_container').hasClass('disabled')) {
                e.preventDefault();
                return false;
            }
            var $dk = $(this).parents('.dk_container').first();

            _openDropdown($dk);

            if ("ontouchstart" in window) {
                $dk.addClass('dk_touch');
                $dk.find('.dk_options_inner').addClass('scrollable vertical');
            }

            e.preventDefault();
            return false;
        });

        // Handle click events on individual dropdown options
        $(document).on('click', '.dk_options a', function (e) {
            var $option = $(this),
                $dk = $option.parents('.dk_container').first(),
                data = $dk.data('dropkick');

            _closeDropdown($dk);
            _updateFields($option, $dk);
            _setCurrent($option.parent(), $dk);

            e.preventDefault();
            return false;
        });

        // Setup keyboard nav
        $(document).bind('keydown.dk_nav', function (e) {
            var// Look for an open dropdown...
                $open = $('.dk_container.dk_open'),
                // Look for a focused dropdown
                $focused = $('.dk_container.dk_focus'),
                // Will be either $open, $focused, or null
                $dk = null;

            // If we have an open dropdown, key events should get sent to that one
            if ($open.length) {
                $dk = $open;
            } else if ($focused.length && !$open.length) {
                // But if we have no open dropdowns, use the focused dropdown instead
                $dk = $focused;
            }

            if ($dk) {
                _handleKeyBoardNav(e, $dk);
            }
        });
    });
})(jQuery, window, document);
